package es.usj.redes.pop3.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;

import es.usj.redes.dao.DAOException;
import es.usj.redes.dao.DAOFactory;
import es.usj.redes.dao.MailJdbcDAO;
import es.usj.redes.dto.Enviado;
import es.usj.redes.dto.Mail;
import es.usj.redes.dto.MailEnviado;
import es.usj.redes.dto.Users;
import es.usj.redes.smtp.utils.Constants;
/**
 * <p>Implementa un servidor de POP3 para correos.</p>
 * <p>
 * Es un servidor monohilo y solo permite la conexion de un usuario a la vez.<br/>
 * Las fases de la comunicación básica están indicadas a continuación:
	<h3>Conexión</h3>
En el protocolo POP3 el servidor debe ponerse a escuchar en el puerto 110, pero como 
esta ocupado por el S.O. se elige el 2000 y el cliente establecerá una conexión en dicho puerto.
Tras recibir la conexión del cliente el servidor responderá con el comando:
<li>{@code +OK Service Ready.<CRLF>}</li>
	
	<h3>Estado del servidor</h3>
Para conocer el estado del servidor el cliente enviará el comando:
<li>{@code STAT<CRLF>}</li>
El servidor responderá con un comando afirmativo indicando el número de mensajes existentes en el servidor y su tamaño total.
<li>{@code +OK<SP><numero de mensajes><SP><tamaño total><CRLF>}</li>
<h3>Listado de mensajes</h3>
Otra opción es enviar un mensaje para conocer los mensajes y sus tamaños.
<li>{@code LIST<CRLF>}</li>
El servidor responderá:
<ul>
<li>{@code +OK<CRLF>}</li>
<li>{@code 1<SP><tamaño del mensaje 1><CRLF>}</li>
<li>{@code 2<SP><tamaño del mensaje 2><CRLF>}</li>
<li>{@code …}</li>
<li>{@code n<SP><tamaño del mensaje n><CRLF>}</li>
<li>{@code .<CRLF>}</li>
</ul>
Es decir, por cada línea se indica el número de mensaje y su tamaño y se finaliza con una línea con un punto.
<h3>Descarga de un mensaje</h3>
Para descargar un mensaje se debe enviar el siguiente comando:
<li>{@code	RETR <SP><numero de mensaje><CRLF>}</li>
El servidor responderá con uno OK:
<li>{@code	+OK}</li>
A esta línea seguirá el mensaje enviándolo de la misma forma que se indica el envío de mensajes en SMTP. 
Al final la última línea también se indica con un solo punto.
Si el mensaje solicitado no existe se debe indicar:
<li>{@code	-ERR message not found.<CRLF>}</li>

El servidor permite también la eliminación de mensajes según la siguiente estructura:
Para eliminar un mensaje se debe enviar al servidor el comando:
<li>{@code	DELE<SP><numero de mensaje>}</li>
Los códigos de respuesta pueden ser:
<li>{@code	+OK Message deleted}</li>
<li>{@code	-ERR message not deleted. The message doesn’t exist. }</li>
Si debido a la implementación puede haber otros errores se deben indicar de la misma forma.

</p>
 * @version 1.6
 * @since 1.0
 */
public class ServerPOP3 {
	private static final int port = 2000;
	public ServerSocket serverSocket = null;
	public Socket socket = null;
	public BufferedReader input = null;
	public PrintWriter output = null;
	public boolean conectado = false;
	private Users user = null;
	private int idUserActual;

	public ServerPOP3() {
		this.acceptConnection(true);
	}
	
	/*
	public ServerPOP3(ServerSocket serverSocket) {
		ServerPOP3.serverSocket = serverSocket;
		this.acceptConnection(false);
	}
	*/

	public static int getPort() {
		return port;
	}

	public boolean isConnected() {
		return conectado;
	}

	public void setidUserActual(int valor) {
		this.idUserActual = valor;
	}

	public int getIdUserActual() {
		return this.idUserActual;
	}

	public void setConnected(boolean valor) {
		this.conectado = valor;
	}

	public void printString(String str) {
		System.out.println(str);
	}

	protected String supressScapeChars(String str) {
		return str.replace("\r\n", "");
	}

	/**
	 * Método que crea la conexión mediante un ServerSOcket y un Socket, se
	 * queda escuchando en el puerto especificado
	 */
	private void acceptConnection(boolean createServerSocket) {
		try {

			// crear el socket de escucha
			
			if (createServerSocket)
			{
				if (serverSocket != null)
					serverSocket.close();
				
				if (socket != null)
					socket.close();
				
				socket = null;
				serverSocket = null;
				
				serverSocket = new ServerSocket(ServerPOP3.getPort());
			}
		
			this.printString("Servidor lanzado, escuchando conexiones entrantes...");
			// aceptar una conexion y crear el socket para la transmision con el
			// cliente
			
			socket = serverSocket.accept();
			
			//socket.bind(serverSocket.getLocalSocketAddress());
			//socket.setReuseAddress(true);
			//socket.setKeepAlive(true);
			
			this.printString("Conexion establecida!");
			setConnected(true);

			// obtener los flujos de entrada/salida del socket
			
			input = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));

			output = new PrintWriter(socket.getOutputStream(), true);
			

			if (this.sendResponse(Constants.POP3_CLIENT_CONNECTION_ESTABLISHED_COMMAND)) {
				System.out.println("Esperando ordenes...");
				this.readInput(input);
			}

			/*
			 * // leer el dato enviado por el cliente dato = entrada.readInt();
			 * System.out.println("recibido: "+dato); mitad = (float)dato / 2 ;
			 * // enviar el resultado al cliente salida.writeFloat(mitad); //
			 * cerrar el socket conectado al cliente
			 */
		} catch (IOException e) {
			
			
			System.out.println("Ha ocurrido un error: " + e);
			this.sendGenericError();
		
		}
	}

	/**
	 * Método de envío de la respuesta
	 * 
	 * @param response
	 *            Respuesta a enviar
	 * @return true si se envía correctamente, en caso contrario false
	 */
	public boolean sendResponse(String response) {
		boolean state = false;

		try {
			this.output.println(response);
			state = true;
			this.printString("Respuesta enviada al cliente: " + response);
		} catch (Exception e) {
			System.out.println("Error al enviar la respuesta");
			// e.printStackTrace();
			this.sendGenericError();
		}

		return state;
	}

	/**
	 * Método que lee la información que le llega y en función de ello emite una
	 * respuesta
	 */
	public void readInput(BufferedReader input) {

		MailJdbcDAO connection = null;
		try {
			connection = DAOFactory.getInstance().getMailDAO();
		} catch (DAOException e1) {
			this.sendGenericError();
			e1.printStackTrace();
		}

		while (isConnected()) {
			try {
				String inputString = input.readLine();

				// obtener las entradas y en funcion de ello enviar las
				// respuestas
	
				this.printString("Host Address: " + socket.getInetAddress().getHostAddress());
				this.printString("Available: " + String.valueOf(socket.getInputStream().available()));
				this.printString(inputString);
				
				if (inputString != null) {

					if (inputString.contains("BYE")) {
						input.close();
						input = null;
						output.close();
						output = null;
						this.setConnected(false);
						acceptConnection(true);
						this.sendResponse("servidor reiniciado");
						this.printString("servidor reiniciado");
					}
					else if (inputString.contains("STAT")) {

						try {

							int numMessages = 0;
							int lengthMessages = 0;

							List<Mail> emails = connection.listAll();

							if (emails.size() > 0) {
								numMessages = emails.size(); // obtener el
																// número de
																// mensajes

								lengthMessages = 0; // obtener el tamaño total
													// del todos los mensajes

								for (Mail e : emails)
									lengthMessages += e.getTexto().length();
							}

							this.sendResponse("+OK " + numMessages + " "
									+ lengthMessages + "\r\n");

						} catch (Exception e) {

							this.sendGenericError();
							e.printStackTrace();
						}

					} else if (inputString.contains("LIST")) {

						try {

							int idUser = Integer.parseInt(inputString
									.split(" ")[1]);

							if (idUser == 0) {
								this.sendGenericError();
								String endLine = ".\r\n";
								this.sendResponse(endLine);
								return;
							}

							setidUserActual(idUser);
							List<MailEnviado> emails = DAOFactory.getInstance()
									.getEnviadoDAO()
									.listEnviadosNoEliminadosFromUser(idUser);// connection.listByFrom(idUser);

							// System.out.println(email);
							this.sendResponse("+OK\r\n");

							if (emails.size() > 0) {
								int index = 1;

								for (MailEnviado i : emails) {
									int size = i.getTexto().length();

									String message = index + " " + size + " "
											+ i.getIdMail() + "\r\n";

									this.sendResponse(message);
									index++;
								}

								String endLine = ".\r\n";
								this.sendResponse(endLine);
							} else {
								this.sendGenericError();
							}

						} catch (Exception e) {

							this.sendGenericError();
							e.printStackTrace();
						}
					} else if (inputString.contains("DELE")) {

						try {
							String idMessage = inputString.trim().replace("DELE", "").replace(" ", "").replace("\r\n", "")
									.toString();
							
							System.out.println(Integer.parseInt(idMessage));
							if (idMessage != null) {
								Enviado mailEnviado = DAOFactory.getInstance()
										.getEnviadoDAO()
										.read(Integer.parseInt(idMessage));
								mailEnviado.setEliminado(true);
								try {
									DAOFactory.getInstance().getEnviadoDAO()
											.update(mailEnviado);
									this.sendResponse("+OK Message deleted");
									this.sendResponse(".\r\n");
								} catch (DAOException e) {
									this.sendResponse("-ERR message not deleted. Ocurrió algun error.");
									this.sendResponse(".\r\n");
								}
							} else {
								this.sendResponse("-ERR message not deleted. The message doesn’t exist.");
								this.sendResponse(".\r\n");
							}
						} catch (Exception e) {
							e.printStackTrace();
							this.sendResponse("-ERR ocurrió algún tipo de error");
						}

						// VERSION ALTERNATIVA
						// try{
						// String idMessage =
						// inputString.split(" ")[1].toString();
						// if (idMessage !=null){
						// List<Enviado> listaCorreosUsuario =
						// DAOFactory.getInstance().getEnviadoDAO().listByUsuario(getIdUserActual());
						//
						// Enviado correoBorrar=listaCorreosUsuario.get(0);
						// correoBorrar.setEliminado(true);
						// try{
						// DAOFactory.getInstance().getEnviadoDAO().update(correoBorrar);
						// this.sendResponse("+OK Message deleted\n\r");
						// for(int i=0; i< listaCorreosUsuario.size() ; i++)
						// //PARA VER EL ESTADO DE LOS MENSAJES
						// System.out.println("Con el id "+i+"  "+listaCorreosUsuario.get(i));
						// }catch(Exception e)
						// {
						// this.sendResponse("-ERR message not deleted. Ocurrió algun error.");
						// }
						// }else{
						// this.sendResponse("-ERR message not deleted. The message doesn’t exist.");
						// }
						// }catch(Exception e){
						// e.printStackTrace();
						// this.sendResponse("malooo aki petaaa");
						// }
						//

					} else if (inputString.contains("RETR")) {

						try {

							// System.out.println("pasa");
							this.sendGenericOK();

							String[] trimmed = inputString.split(" ");

							int idEmail = Integer.parseInt(trimmed[1]
									.toString());

							// System.out.println("idEmail: " + idEmail);

							Mail email = null;
							try {
								email = connection.read(idEmail);
							} catch (Exception e) {

								// e.printStackTrace();

								this.sendGenericError();
								this.sendResponse(".\r\n");

								return;
							}

							if (email == null) {
								this.sendEmailNotFoundError();
							} else {
								// System.out.println("string email"+
								// email.toString());
								this.sendResponse("idMail=" + email.getIdMail()
										+ "\r\n");
								this.sendResponse("idUsuario="
										+ email.getIdUsuario() + "\r\n");
								this.sendResponse("from=" + email.getFrom()
										+ "\r\n");
								this.sendResponse("subject="
										+ email.getSubject() + "\r\n");
								this.sendResponse("texto=" + email.getTexto()
										+ "\r\n");
								this.sendResponse("fechaEnviado="
										+ email.getFechaEnviado());
								// this.sendResponse(email.getTo() + "\r\n");

								this.sendResponse(".\r\n");
							}

						} catch (NumberFormatException e) {
							// e.printStackTrace();

							this.sendGenericError();
							this.sendResponse(".\r\n");
						}
					} else if (inputString.contains("USER")) {

						try {

							String username = inputString.trim().replace("USER", "").toString();

							System.out.println("Username=" + username);
							
							user = DAOFactory.getInstance().getUsersDAO()
									.read(username.trim());

							if (user != null) {
								System.out.println("distinto null");
								this.sendResponse("+OK Password required.\r\n");
							} else {
								System.out.println("daofactory null");
								this.sendResponse("-ERR Unkown user.\r\n");
							}

						} catch (Exception e) {
							this.sendResponse("-ERR Unkown user.\r\n");
							 e.printStackTrace();
						}
					} else if (inputString.contains("PASS")) {

						try {
							String password = inputString.split(" ")[1]
									.toString();
							String bbddPassword = user.getPassword().replace(
									" ", ""); // me lo da con un espacio, mirar
												// la codificacion al obtener el
												// pass de la bbdd
							// System.out.println(bbddPassword);

							if (password.equals(bbddPassword)) {
								this.sendResponse("+OK User logged in.\r\n");
							} else {
								this.sendResponse("-ERR Login Failed.\r\n");
							}

						} catch (Exception e) {

							this.sendResponse("-ERR Login Failed.\r\n");
							// e.printStackTrace();
						}

					} else  {
						 //System.out.println("Mensaje de info: No hay datos de entrada");
					}
				}

			} catch (IOException e) {
				// e.printStackTrace();
				this.sendGenericError();
			}
		}
	}

	/**
	 * Método de envío de email no encontrado
	 */
	private void sendEmailNotFoundError() {
		try {
			this.output.println("-Err message not found.\r\n");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Método que cierra la conexión del servidor
	 * 
	 * @param serverSocket
	 *            Variable socket sobre la que deseamos cerrar la conexión
	 */
	public void closeServerSocket(ServerSocket serverSocket) {
		// this.socket.close();
		// serverSocket.close();

		this.printString("Conexion cerrada");
	}

	/**
	 * Método de envío de un error genérico
	 */
	public void sendGenericError() {
		try {
			this.output.println("-Err\r\n");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Método de envío de un OK genérico
	 */
	public void sendGenericOK() {
		try {
			this.output.println("+OK\r\n");
		} catch (Exception e) {
			// e.printStackTrace();
			this.sendGenericError();
		}
	}

	
	public static void main(String[] args) {
		new ServerPOP3();
	}
	
}
